<?php
// +----------------------------------------------------------------------
// | Project 项目服务
// +----------------------------------------------------------------------
// | 主要服务于Project数据处理
// +----------------------------------------------------------------------
// | 错误编码头 221xxx
// +----------------------------------------------------------------------

namespace common\service;

use common\exception\LogicException;
use common\model\AttributeValueModel;
use common\model\EntityModel;
use common\model\EpisodeAllocationModel;
use common\model\MediaModel;
use common\model\PlanModel;
use common\model\ProjectCheckListModel;
use common\model\ProjectMemberModel;
use common\model\ProjectModel;
use common\model\ProjectStepReviewWorkflowRelationModel;
use common\model\ProjectTemplateModel;
use common\model\StatusModel;
use common\model\StepCategoryModel;
use common\model\StepModel;
use common\model\StepWorkflowModel;
use common\model\TaskModel;
use common\model\UserModel;
use common\model\VersionModel;
use support\ErrorCode;
use support\SingletonTrait;
use think\Exception;
use think\module\Module;
use think\Tree;
use Webman\Stomp\Client;

class ProjectService
{

    // 单例工具
    use SingletonTrait;

    public function __construct()
    {
    }

    /**
     * 获得单个项目信息
     * @param $projectId
     * @param $field
     * @param false $throwNotFound
     * @return array|false|string|null
     */
    public function getProject($projectId, $field, $throwNotFound = false)
    {
        $projectModel = model(ProjectModel::class);
        $project = $projectModel->where(['id' => $projectId])
            ->field($field)
            ->find();
        if ($throwNotFound === true && empty($project)) {
            throw new LogicException("project not found", ErrorCode::PROJECT_NOT_FOUND);
        }
        return $project;
    }

    /**
     * 获得多个项目信息
     * @param $where
     * @param $field
     * @return mixed
     */
    public function getProjects($where, $field)
    {
        $projectModel = model(ProjectModel::class);
        return $projectModel->where($where)
            ->field($field)
            ->cache(C('database.default_query_cache_time'))
            ->select();
    }

    /**
     * 初始化项目工序计划
     * @param int $projectId
     * @param int $entityId
     * @return array
     * @throws \Exception
     */
    public function initProjectStepCategoryPlan($projectId, $entityId = 0)
    {
        $stepCategoryModel = model(StepCategoryModel::class);
        $templateService = TemplateService::getInstance();

        $project = $this->getProject($projectId, 'project_template_id,start_time,end_time,delivery_time');

//        $entity = model(EntityModel::class)->find($entityId);
//        $startTime = $entity['start_time'];
//        $endTime = $entity['end_time'];

        $startTime = $project['start_time'];
        $endTime = $project['end_time'] > 0 ? $project['end_time'] : $project['delivery_time'];

        if ($startTime <= 0 || $endTime <= 0) {
            throw new LogicException("entity did not set start_time or end_time", ErrorCode::ENTITY_NOT_SET_START_TIME_OR_END_TIME);
        }

        $duration = $endTime - $startTime;

        $categories = $stepCategoryModel
            ->field("id,parent_id,name,index")
            ->order("index ASC")
            ->select();
        $settings = $templateService->getConfigSetting(["id" => $project['project_template_id']], "step_category_plan");
        if (empty($settings) || $duration <= 0 || empty($settings['tasks'])) { //如果没有配置计划模板 那么直接返回分类
            return $categories;
        }
        $tasks = $settings['tasks'] ?? [];
        $categoryData = array_column($categories, null, 'id');
        $parentCategoryMap = array_column($categories, null, 'parent_id');

        $addRows = [];
        $planModel = model(PlanModel::class);

        /**
         * 排序 按照工序分类 排好模板的顺序
         * 顺序计算 开始时间
         * 倒序计算 结束时间
         * 根据计算出的二级分类时间 计算父级分类的开始时间和结束时间
         */


        //排序 按照工序分类 排好模板的顺序
        if (!isset($tasks[0]['step_category_index'])) {
            $tasks = array_map(function (&$item) use ($categoryData) {
                $item['step_category_index'] = $categoryData[$item['step_category_id']]['index'];
                return $item;
            }, $tasks);
        }
        $tasks = array_sort_by($tasks, 'step_category_index');

        // 待计算的父级分类
        $parentCategory = [];
        $lastUnitLongest = 0;
        $lastStartTime = $startTime;
        // 生成开始时间
        foreach ($tasks as $task) {
            if (!isset($categoryData[$task['id']])) {
                continue;
            }
            $category = $categoryData[$task['id']];

            // 是否是最后一级分类

            $hasChild = array_key_exists($category['id'], $parentCategoryMap);

            if ($hasChild) {
                $parentCategory[] = $category;
                continue;
            }
            $tmp = $category;
            $tmp['start_time'] = $lastStartTime + $lastUnitLongest;
            $addRows[] = [
                'tenant_id' => auto_fill_tenant_id(),
                'step_category_id' => $category['id'],
                'project_id' => $projectId,
                'start_time' => $tmp['start_time'],
                'end_time' => 0,
                'entity_id' => $entityId,
                'uuid' => create_uuid(),
            ];
            $lastStartTime = $tmp['start_time'];
            $lastUnitLongest = $task['unit_longest_duration'];
        }
        // 生成结束时间
        $addRows = array_column($addRows, null, 'step_category_id');
        $reverseTask = array_reverse($tasks);

        $lastEndTime = $endTime;
        $lastUnitLongest = 0;
        foreach ($reverseTask as $task) {
            if (!isset($categoryData[$task['id']])) {
                continue;
            }
            $category = $categoryData[$task['id']];
            // 是否是最后一级分类
            $hasChild = array_key_exists($category['id'], $parentCategoryMap);

            if ($hasChild) {
                continue;
            }

            // 如果时间不够 结束时间直接等于 开始时间
            if ($addRows[$category['id']]['start_time'] >= ($lastEndTime - $lastUnitLongest)) {
                $lastEndTime = $addRows[$category['id']]['end_time'] = $addRows[$category['id']]['start_time'];
            } else {
                $lastEndTime = $addRows[$category['id']]['end_time'] = $lastEndTime - $lastUnitLongest;
            }

            $lastUnitLongest = $task['unit_longest_duration'];
        }

        // 生成工序父级分类的时间
        $tree = new Tree("id", 'parent_id');
        $tree->load($categories);

        $categoryTree = $tree->DeepTree();
        foreach ($parentCategory as $category) {
            $allChildrenIds = [];
            $this->seekAllChildren($categoryTree, $category['id'], $allChildrenIds);
            $allChildrenIds = array_column($allChildrenIds, 'id', 'id');
            $tmpChildrenPlan = array_filter($addRows, function ($item) use ($allChildrenIds) {
                return in_array($item['step_category_id'], $allChildrenIds);
            });

            if (empty($tmpChildrenPlan)) {
                continue;
            }

            $categoryStartTime = min(array_column($tmpChildrenPlan, 'start_time'));
            $categoryEndTime = max(array_column($tmpChildrenPlan, 'end_time'));
            $addRows[] = [
                'tenant_id' => auto_fill_tenant_id(),
                'step_category_id' => $category['id'],
                'project_id' => $projectId,
                'start_time' => $categoryStartTime,
                'end_time' => $categoryEndTime,
                'entity_id' => $entityId,
                'uuid' => create_uuid()
            ];
        }
        $addRows = array_values($addRows);

        $oldRows = $planModel->where(['project_id' => $projectId, "entity_id" => $entityId])->select();

        $needAdd = $addRows;
        $needUpdate = $needDelete = [];
        if (!empty($oldRows)) {
            $oldRowsAssoc = array_column($oldRows, null, 'step_category_id');
            $stepCategoryData = array_column($addRows, 'step_category_id');
            $oldStepCategoryData = array_column($oldRows, 'step_category_id');

            $updateCategoryIds = array_intersect($stepCategoryData, $oldStepCategoryData);
            $addCategoryIds = array_diff($oldStepCategoryData, $stepCategoryData);
            $deleteCategoryIds = array_diff($stepCategoryData, $oldStepCategoryData);

            // 需要添加的
            $needAdd = array_filter($addRows, function ($item) use ($addCategoryIds) {
                return in_array($item['step_category_id'], $addCategoryIds);
            });

            // 需要更新的
            $needUpdate = array_filter($addRows, function ($item) use ($updateCategoryIds, $oldRowsAssoc) {
                return in_array($item['step_category_id'], $updateCategoryIds);
            });

            // 添加主键
            $needUpdate = array_map(function ($item) use ($oldRowsAssoc) {
                $item['id'] = $oldRowsAssoc[$item['step_category_id']]['id'];
                return $item;
            }, $needUpdate);


            // 需要删除的
            $needDelete = array_filter($oldRows, function ($item) use ($deleteCategoryIds) {
                return in_array($item['step_category_id'], $deleteCategoryIds);
            });
            $needDelete = array_map(function ($item) {
                return $item['id'];
            }, $needDelete);
        }

        if (!empty($needAdd)) {
            $planModel->addAll($needAdd);
        }
        if (!empty($needUpdate)) {
            $planModel->saveAll($needUpdate);
        }

        if (!empty($needDelete)) {
            $planModel->delete([
                'where' => ["id" => ['IN', implode(',', $needDelete)]]
            ]);
        }


        $result = array_map(function ($item) use ($categoryData) {
            return [
                'id' => $item['step_category_id'],
                'name' => $categoryData[$item['step_category_id']]['name'],
                'parent_id' => $categoryData[$item['step_category_id']]['parent_id'],
                'start_time' => $item['start_time'],
                'end_time' => $item['end_time'],
            ];
        }, $addRows);

        return $result;
    }


    /**
     * 处理 前端状态筛选的map
     * @param $status
     * @return array
     */
    public function filterByStatus($status)
    {
        switch ($status) {
            case "not_started":
                $statusCorrespond = ['not_started'];
                break;
            case "inprogress":
                $statusCorrespond = [
                    'in_progress',
                    'daily',
                    'feedback',
                ];
                break;
            case "blocked":
                $statusCorrespond = [
                    'blocked'
                ];
                break;
            case "done":
                $statusCorrespond = [
                    'done',
                    'publish',
                ];
                break;
            case "history":
                $statusCorrespond = [
                    'done',
                    'hide',
                    'blocked',
                ];
                break;
            case "total":
            default:
                $statusCorrespond = [
                    'done',
                    'hide',
                    'blocked',
                    'not_started',
                    'in_progress',
                    'daily',
                    'feedback',
                    'publish',
                ];
        }
        return $statusCorrespond;
    }

    /**
     * 克隆一个影子项目
     * @param array $project
     * @param int $tenantId
     * @param array $user 分配的用户
     * @return int
     * @throws Exception
     * @throws \Exception
     */
    public function shadowProject($project, $tenantId, $user)
    {
        $projectModel = model(ProjectModel::class);
        $projectShadow = $project;
        unset($projectShadow['id']);
        $projectShadow['code'] = $project['code'] . '_outsource_' . $tenantId;
        $projectShadow['name'] = $project['name'];
        $projectShadow['type'] = 'outsource';
        $projectShadow['description'] = $project['description'];
        $projectShadow['work_dir'] = $project['work_dir'];
        $projectShadow['tenant_id'] = $tenantId;
        $projectShadow['belong_project_id'] = $project['id'];
        $projectShadow['delivery_time'] = $project['delivery_time'];
        $projectShadow['json'] = $project['json'];
        $projectShadow['uuid'] = create_uuid();
        $projectShadow['created_by'] = $user['id'];
        $projectShadow['created'] = time();

        $newProjectId = $projectModel->add($projectShadow, ['auto' => []]);
        if ((int)$newProjectId <= 0) {
            throw new LogicException('clone project failure' . $projectModel->getError(), ErrorCode::PROJECT_CLONE_FAILURE);
        }
        return $newProjectId;
    }

    /**
     * 复制项目模板配置到项目配置 (不会重复复制)
     * @param int $projectId
     * @return int
     */
    public function copyProjectTemplateRelateConfig(int $projectId)
    {
        $project = model(ProjectModel::class)->find($projectId);
        $res = 0;
        if ($project['project_template_id'] > 0) {
            $projectTemplateId = $project['project_template_id'];

            // 检查是否复制过配置
            $existCheckList = model(ProjectCheckListModel::class)->where(['project_id' => $projectId])->find();
            $existStepReviewRelation = model(ProjectStepReviewWorkflowRelationModel::class)->where(['project_id' => $projectId])->find();

            if (empty($existCheckList)) {
                $res += model(ProjectCheckListModel::class)->execute('insert into project_check_list 
    ( uuid,check_list_id, project_id, review_role_config) 
    SELECT uuid,id,' . $projectId . ',review_role_config from check_list where check_list.project_template_id = ' . $projectTemplateId);
            }

            if (empty($existStepReviewRelation)) {
                $res += model(ProjectStepReviewWorkflowRelationModel::class)->execute('insert into project_step_review_workflow_relation 
    ( step_id, review_workflow_id, uuid, project_id, attr_str, step_workflow_id) 
    SELECT step_id,review_workflow_id,uuid,' . $projectId . ',attr_str,step_workflow_id from step_review_workflow_relation where project_template_id = ' . $projectTemplateId);
            }
        }
        return $res;
    }

    /**
     * 查询节点 并收集儿子
     * @param $tree
     * @param $pid
     * @param $res
     */
    public static function seekAllChildren($tree, $pid, &$res)
    {
        foreach ($tree as $item) {
            if ($item['id'] != $pid && !empty($item['children'])) {
                self::seekAllChildren($item['children'], $pid, $res);
            } else {
                if (!isset($item['children'])) {
                    return;
                }
                self::collectChildren($item['children'], $res);
            }
        }
    }

    /**
     * 收集儿子
     * @param $tree
     * @param $res
     * @return array
     */
    public static function collectChildren($tree, &$res)
    {
        foreach ($tree as $item) {
            $res[] = $item;
            if (!empty($item['children'])) {
                static::collectChildren($tree, $res);
            }
        }
        return $res;
    }

    /**
     * 删除项目
     * @param $param
     * @return array
     * @throws \Throwable
     */
    public function delete($param)
    {
        $projectModel = model(ProjectModel::class);
        $entityModel = model(EntityModel::class);
        $taskModel = model(TaskModel::class);
        $versionModel = model(VersionModel::class);
        $episodeAllocationModel = model(EpisodeAllocationModel::class);
        $planModel = model(PlanModel::class);
        $projectMemberModel = model(ProjectMemberModel::class);

        $params = $param['param'];
        $params['fields'] = "id";
        $projectList = $projectModel->where($param['param']['filter'])->select();

        if (empty($projectList)) {
            return [];
        }
        $projectIdData = array_column($projectList, 'id');
        $deleteFilter = [
            'project_id' => ["IN", implode(',', $projectIdData)],
        ];

        $projectIds = implode(',', $projectIdData);
        $projectModel->startTrans();
        try {
            // 项目删除
            $projectModel->delete(implode(',', $projectIdData));
            // 实体删除
            $entityModel->where($deleteFilter)->delete();
            // 关联删除 任务和任务关联关系
            $taskModel->execute("DELETE task,task_relation FROM task LEFT JOIN  task_relation ON task.id = task_relation.source_task_id WHERE task.project_id IN ($projectIds)");
            // 版本删除
            $versionModel->execute("DELETE version,version_check_list FROM version LEFT JOIN version_check_list ON version.id = version_check_list.version_id WHERE version.project_id IN ($projectIds)");
            // 剧集分配删除
            $episodeAllocationModel->where($deleteFilter)->delete();
            // 项目计划删除
            $planModel->where($deleteFilter)->delete();
            // 项目成员分类删除
            $projectMemberModel->where($deleteFilter)->delete();
            $projectModel->commit();
            return ['ids' => $projectIdData];
        } catch (\Throwable $e) {
            $projectModel->rollback();
            trace(format_exception_info($e), 'ERR');
            throw $e;
        }
    }


    /**
     * 获取指定用户项目列表
     * @param $userId
     * @param $param
     * @param bool $filterRoleResourceByProjectMember 是否使用项目成员做资源筛选
     * @param string $appCode 当前应用code
     * @return array
     * @throws \Exception
     */
    public function getUserProjectList($userId, $param, $filterRoleResourceByProjectMember = false, $appCode = 'teamones_client')
    {
        $order = $param['param']['order'] ?? "project.created DESC";
        $page = $param['param']['page'] ?? [1, C("database.database_max_select_rows")];
        $selectShowStatus = $param['param']['select_show_status'] ?? "no";
        /**
         * 根据项目成员配置查询可见的任务列表
         */

        $projectIdFilter = $this->getProjectIdFilterByProjectMember($userId);
        $filter = $param['param']['filter'] ?? [];
        if (empty($projectIdFilter)) {
            return ['total' => 0, 'rows' => []];
        }
        $filter['project.id'] = $filter["project.id"] ?? ['IN', implode(',', $projectIdFilter)];

        $statusFilter = $param['param']['project_status'] ?? null;

        $statusCorrespond = $this->filterByStatus($statusFilter);
        $statusData = model(StatusModel::class)->selectData([
            "filter" => ["correspond" => ["IN", $statusCorrespond]],
            "fields" => "id",
        ]);

        $statusData = array_column($statusData['rows'], 'id', 'id');
        $filter["project_status.id"] = ["IN", join(',', $statusData)];
        $filter["project.is_delete"] = 'no';
        $presetFields = [
            "project.id",
            "project.tenant_id",
            "project.name",
            "project.code",
            "project.type",
            "project.belong_project_id",
            "project.public",
            "project.is_demo",
            "project.project_template_id",
            "project.rate",
            "project.description",
            "project.start_time",
            "project.end_time",
            "project.duration",
            "project.resolution",
            "project.delivery_time",
            "project.delivery_platform",
            "project.episodes",
            "project.created_by",
            "project.created",
            "project_status.id",
            "project_status.name",
            "project_status.code",
            "project.drama_type",
        ];
        $fields = array_unique(array_merge($presetFields, explode(',', $param['param']['fields'] ?? null)));
        $fields = array_filter($fields, function ($item) {
            return !empty($item);
        });


        $res = model(ProjectModel::class)->selectData([
            "filter" => $filter,
            "fields" => implode(',', $fields),
            "order" => $order,
            'page' => $page,
        ]);

        if (empty($res['rows'])) {
            return $res;
        }

        // 追加项目资源信息
        if ($filterRoleResourceByProjectMember) {
            $this->appendProjectRoleResource($res, $userId, $appCode);
        }

        if ($selectShowStatus == "yes") {
            $episdoeModule = module_by_code("episode");
            $projectIds = [];
            foreach ($res['rows'] as $row) {
                if ($row['project_status']['code'] == 'inprogress') {
                    $projectIds[] = $row['project']['id'];
                }
            }
            $episodeFilter = [
                'project_id' => ['IN', $projectIds],
                'module_id' => $episdoeModule['id'],
                'code' => ['LIKE', "EP%"],
                "_string" => "json_extract(json, '$.episode_is_show') = 'yes'",
            ];
            $projectShowEpisodes = $projectSealEpisodes = $projectPlanShowEpisodes = [];
            if (!empty($projectIds)) {
                // 获取每个项目的播出统计
                $projectShowEpisodes = model(EntityModel::class)
                    ->field("project_id, count(*) as count")
                    ->where($episodeFilter)
                    ->group("project_id")
                    ->select();
                $projectShowEpisodes = array_column($projectShowEpisodes, null, 'project_id');
                // 获取每个项目的封板统计
                $episodeFilter["_string"] = "json_extract(json, '$.episode_is_seal') = 'yes'";
                $projectSealEpisodes = model(EntityModel::class)
                    ->field("project_id, count(*) as count")
                    ->where($episodeFilter)
                    ->group("project_id")
                    ->select();
                $projectSealEpisodes = array_column($projectSealEpisodes, null, 'project_id');
                // 获取播出统计为0的项目ID
                $notShowEpisodeProjectIds = [];
                $projectPlanShowEpisodes = [];
                foreach ($projectIds as $id) {
                    if (!array_key_exists($id, $projectShowEpisodes)) {
                        $notShowEpisodeProjectIds[] = $id;
                    }
                }
                if ($notShowEpisodeProjectIds) {
                    $episodeFilter['project_id'] = ['IN', $notShowEpisodeProjectIds];
                    $episodeFilter["_string"] = "json_extract(json, '$.episode_plan_show_time') > 0";
                    $projectPlanShowEpisodes = model(EntityModel::class)
                        ->field("min(id) as id,json_unquote(json_extract(json,'$.episode_plan_show_time')) as episode_plan_show_time,project_id")
                        ->where($episodeFilter)
                        ->group("project_id")
                        ->select();
                    $projectPlanShowEpisodes = array_column($projectPlanShowEpisodes, null, 'project_id');
                }
            }
            $res["rows"] = array_map(function ($item) use ($projectShowEpisodes, $projectSealEpisodes, $projectPlanShowEpisodes) {
                $item['show_episode_count'] = $projectShowEpisodes[$item['project']['id']]['count'] ?? 0;
                $item['seal_episode_count'] = $projectSealEpisodes[$item['project']['id']]['count'] ?? 0;
                $item['plan_show_time'] = $projectPlanShowEpisodes[$item['project']['id']]['episode_plan_show_time'] ?? 0;
                return $item;
            }, $res["rows"]);
        }
        return $res;
    }

    /**
     * 创建项目
     * @param $param
     * @param $currentModuleData
     * @return array
     * @throws \Throwable
     */
    public function create($param, $currentModuleData)
    {
        $projectModel = model(ProjectModel::class);
        $projectMemberData = [
            'user_id' => request()->getUserId(),
            'project_id' => 0,
            'role_code' => TaskService::EXECUTIVE_PRODUCER_CODE,
            'step_category_ids' => '',
            'role_id' => '',
            'type' => 'manage'
        ];
        $role = teamones_request('teamones-im', 'role/find', ['param' => ['filter' => ['code' => TaskService::EXECUTIVE_PRODUCER_CODE]]])['data'];
        if (!empty($role)) {
            $projectMemberData['role_id'] = $role['id'];
        }

        $projectMemberData['team_id'] = 0;
        $stepCategoryData = model(StepCategoryModel::class)->where(['code' => $projectMemberData['role_code']])->field('id')->find();
        if (!empty($stepCategoryData['id']) && !empty($projectMemberData['role_id'])) {
            $matchStepCategoryNameTeamId = ProjectTeamService::getInstance()->matchStepCategoryNameTeamData($projectMemberData['role_id'], [$stepCategoryData['id']]);
            $projectMemberData['team_id'] = $matchStepCategoryNameTeamId;
        }
        $projectModel->startTrans();
        try {
            $commonService = CommonService::instance($currentModuleData['code']);
            $res = $commonService->commonCreate($param, $currentModuleData);
            $errCode = $commonService->getErrorCode();
            if ($errCode === -411111) {
                throw new LogicException($commonService->getErrorMessage(), $errCode);
            }
            $projectId = array_pop($res);
            $projectMemberData['project_id'] = $projectId;
            // 初始化项目成员信息
            $projectMemberCreateRes = model(ProjectMemberModel::class)->addItem($projectMemberData);
            if ($projectMemberCreateRes === false) {
                throw new LogicException(model(ProjectMemberModel::class)->getError(), model(ProjectMemberModel::class)->getErrorCode());
            }
            $projectModel->commit();
            // 生成项目集信息
            $project = $projectModel->find($projectId);
            // 如果项目模板id存在 那么复制模板配置
            if ($project['project_template_id'] > 0) {
                $this->copyProjectTemplateRelateConfig($projectId);
            }
            if ((int)$project['episodes'] > 0) {
                Client::send("generate_project_episode", build_queue_data_with_xu(["project_id" => $projectId]));
            }
            return $project;
        } catch (\Throwable $e) {
            $projectModel->rollback();
            throw $e;
        }
    }

    /**
     * 项目动态
     */
    public function getAllDynamic()
    {

    }

    /**
     * 项目查询 无用户自动筛选
     * @param $param
     * @return array
     */
    public function baseSelect($param)
    {
        $filter = $param['filter'] ?? [];
        $page = $param['page'] ?? [1, C("database.database_max_select_rows")];
        $order = $param['order'] ?? "project.id DESC";
        $fields = $param['fields'] ?? "*";

        $fields = build_complex_query_fields(explode(',', $fields), '__');

        $total = model(ProjectModel::class)
            ->join("project_template ON project.project_template_id = project_template.id", 'LEFT')
            ->join("status ON json_unquote(json_extract(project.json,'$.project_status')) = status.id", 'LEFT')
            ->where($filter)
            ->count();

        $rows = model(ProjectModel::class)
            ->join("project_template ON project.project_template_id = project_template.id", 'LEFT')
            ->join("status ON json_unquote(json_extract(project.json,'$.project_status')) = status.id", 'LEFT')
            ->where($filter)
            ->field($fields)
            ->page($page[0], $page[1])
            ->order($order)
            ->select();

        foreach ($rows as &$row) {
            $row = separate_complex_query_data($row, "__");
        }


        return ['total' => $total, 'rows' => $rows];
    }

    /**
     * 批量创建项目成员 单角色
     * @param $data
     * @param string $roleCode
     * @return array
     * @throws \Exception
     */
    public function batchCreateProjectMember($data, string $roleCode)
    {
        $res = [];

        $userList = $data['user_list'];

        $users = model(ProjectMemberModel::class)->where(['project_id' => $data['project_id'], 'role_code' => $roleCode])->select();
        $usersGroupByUserId = array_column($users, null, 'user_id');
        $userIds = [];
        foreach ($userList as $user) {
            $userIds[] = $user['user_id'];
            if (isset($usersGroupByUserId[$user['user_id']])) {
                continue;
            }
            $addData = [
                'project_id' => $data['project_id'],
                'role_code' => $user['role_code'],
                'user_id' => $user['user_id'],
            ];
            $res[] = model(ProjectMemberModel::class)->addItem($addData);
        }
        $deleteUserId = array_diff(array_keys($usersGroupByUserId), $userIds);

        if (!empty($deleteUserId)) {
            model(ProjectMemberModel::class)
                ->where([
                    'user_id' => ['IN', implode(',', $deleteUserId)],
                    'project_id' => $data['project_id'],
                ])
                ->delete();
        }

        return $res;
    }

    /**
     * 批量创建项目成员
     * @param $data
     * @return array
     * @throws \Exception
     */
    public function batchCreateProjectMemberMultiRole($data)
    {
        $res = [];

        $userList = $data['user_list'];
        $roleCodeList = array_column($data['user_list'], 'role_code');
        $roleCodeList = array_unique($roleCodeList);

        /**
         * 在一个项目内 一个组内 一个角色下 用户应该是唯一的
         */
        $users = model(ProjectMemberModel::class)
            ->where(['project_id' => $data['project_id'], 'role_code' => ['IN', $roleCodeList]])
            ->select();
        $usersGrouped = array_group_by($users, 'role_code');
        foreach ($usersGrouped as &$users) {
            $users = array_group_by($users, 'user_id');
        }

        $addData = [];
        $tmpProjectMemberArr = [];
        foreach ($userList as $user) {

            $type = $user['type'] ?? ProjectMemberModel::PRODUCTION_TYPE;
            $stepCategoryIds = $user['step_category_ids'] ?? '';

            $stepCategoryIdsTmpArr = [];
            if (!empty($usersGrouped[$user['role_code']][$user['user_id']])) {
                $stepCategoryIdsTmpArr = array_column($usersGrouped[$user['role_code']][$user['user_id']], 'step_category_ids');
            }
            // 已经存在
            if (
                !empty($usersGrouped[$user['role_code']][$user['user_id']][0]['type'])
                && $usersGrouped[$user['role_code']][$user['user_id']][0]['type'] == $type
                && in_array($stepCategoryIds, $stepCategoryIdsTmpArr)
            ) {
                continue;
            }
            $tmpValue = [ // 去除重复的数组
                'project_id' => $data['project_id'],
                'role_code' => $user['role_code'],
                'role_id' => $user['role_id'] ?? 0,
                'user_id' => $user['user_id'],
                'type' => $type,
                'step_category_ids' => $user['step_category_ids'] ?? ''
            ];
            $tmpProjectMemberStr = '';
            foreach ($tmpValue as $tmpKey => $tmpItem) {
                $tmpProjectMemberStr .= '-' . $tmpKey . '-' . $tmpItem;
            }
            if (in_array($tmpProjectMemberStr, $tmpProjectMemberArr)) {
                continue;
            }
            $tmpProjectMemberArr[] = $tmpProjectMemberStr;
            $addData[] = [
                'project_id' => $data['project_id'],
                'role_code' => $user['role_code'],
                'role_id' => $user['role_id'] ?? 0,
                'user_id' => $user['user_id'],
                'type' => $type,
                'step_category_ids' => $user['step_category_ids'] ?? '',
                'uuid' => create_uuid(),
            ];
        }
        if (!empty($addData)) {
            $res = model(ProjectMemberModel::class)->addAll($addData);
        }

        return $res;
    }

    /**
     * 单个创建项目成员
     * @param $data
     * @return array|bool
     * @throws \Exception
     */
    public function singleCreateProjectMember($data)
    {
        $projectMemberModel = model(ProjectMemberModel::class);
        $users = $projectMemberModel->where(['project_id' => $data['project_id']])->select();
        $usersGroupByUserId = array_column($users, null, 'user_id');

        if (isset($usersGroupByUserId[$data['user_id']])) {
            throw_strack_exception('data already exists.', \think\exception\ErrorCode::DATA_ALREADY_EXISTS);
        }

        $addData = [
            'project_id' => $data['project_id'],
            'role_code' => $data['role_code'],
            'user_id' => $data['user_id'],
        ];
        return $projectMemberModel->addItem($addData);
    }

    /**
     * 快速流程设置
     * @param $data
     * @return mixed
     * @throws Exception
     */
    public function setFastFlow($data)
    {
        $projectModel = model(ProjectModel::class);
        $project = $projectModel->find($data['id']);
        if ($project['fast_workflow'] == "yes") {
            throw_strack_exception("can`t set fast_workflow when fast_workflow is yes", ErrorCode::ALREADY_FAST_WORKFLOW);
        }
        $res = $projectModel->modifyItem([
            'id' => $data['id'],
            'fast_workflow' => $data['fast_workflow'],
        ]);
        if ($res === false) {
            throw_strack_exception($projectModel->getError(), $projectModel->getErrorCode());
        }
        return $res;
    }

    /**
     * 获取项目成员列表
     * @param $userId
     * @param $param
     * @return mixed
     * @throws \Exception
     */
    public function getTeamUserProjectList($userId, $param)
    {
        $res = $this->getUserProjectList($userId, $param);
        return $res;
    }

    /**
     * 模块字段规则配置
     * @param $filter
     * @return array|mixed
     */
    public function getModuleFieldRuleSetting($filter)
    {
        $moduleCode = $filter['module_code'] ?? null;
        $project = $this->getProject($filter['project_id'], 'id,project_template_id');
        if (empty($project)) {
            throw_strack_exception('project not found', ErrorCode::PROJECT_NOT_FOUND);
        }
        $templateService = TemplateService::getInstance();
        $config = $templateService->getProjectTemplateModuleFieldConfig($project['project_template_id']);
        if (!empty($moduleCode)) {
            if (isset($config[$filter['module_code']]) && isset($config[$filter['module_code']]['children'])) {
                $res = $config[$filter['module_code']]['children'];
            } else {
                $res = [];
            }
        } else {
            $res = $config;
        }
        return $res;
    }

    /**
     * 同步主项目的集计划
     * @param $episodeId
     * @param $belongProjectId
     * @param $childProjectId
     * @throws \Exception
     */
    public function syncEpisodePlan($episodeId, $belongProjectId, $childProjectId)
    {
        $currentEpisode = model(EntityModel::class)->find($episodeId);
        $oldEpisode = model(EntityModel::class)->where(['code' => $currentEpisode['code'], 'project_id' => $belongProjectId, 'module_id' => Module::$moduleDictData['module_index_by_code']['episode']['id']])->find();
        $oldPlan = PlanService::getInstance()->getPlan(['entity_id' => $oldEpisode['id'], 'type' => 'episode']);
        if (!empty($oldPlan)) {
            $oldPlan['entity_id'] = $currentEpisode['id'];
            $oldPlan['project_id'] = $childProjectId;
            unset($oldPlan['id']);
            model(PlanModel::class)->add($oldPlan);
        }
    }

    /**
     * 获得任务所有的子任务项目id
     * @param $projectId
     * @return array
     */
    public function getProjectAllSubProject($projectId)
    {
        $projectList = model(ProjectModel::class)->where(['belong_project_id' => $projectId, '_logic' => 'or'])->field('id')->select();
        return array_column($projectList, 'id');
    }

    /**
     * 获取项目的父级项目ID
     * @param $projectId
     * @param $recursive bool 查询根项目
     * @return mixed|void
     */
    public function getProjectParentProjectId($projectId, bool $recursive = false)
    {
        $projectList = model(ProjectModel::class)
            ->field('id, if(belong_project_id = 0, id, belong_project_id) as parent_id')
            ->select();
        $projectBelongList = array_column($projectList, 'parent_id', 'id');
        if ($recursive) {
            foreach ($projectList as $project) {
                $stack = [$project['id']];
                $finalProjectId = 0;
                while (!empty($stack)) {
                    if ($finalProjectId > 0) {
                        $id = array_pop($stack);
                        $projectBelongList[$id] = $finalProjectId;
                        continue;
                    }
                    // 当前项目 不存在父级项目、指向自己、存在环路
                    $id = $stack[array_key_last($stack)];
                    if ((!isset($projectBelongList[$id])) || $projectBelongList[$id] == $id || in_array($projectBelongList[$id], $stack)) {
                        $finalProjectId = $id;
                        continue;
                    }
                    $stack[] = $projectBelongList[$id];
                }
            }
        }
        return $projectBelongList[$projectId];
    }

    /**
     * 扫描已存在数据到项目成员
     * @param $projectId
     * @return array
     * @throws \Exception
     */
    public function scanExistDataToProjectMember($projectId)
    {
        /**
         *
         * 1. 项目创建人
         * 2. 剧集分配
         * 3. 任务数据(分配制片,分配组长,执行人)
         *
         */

        $res = [];
        // 项目创建人
        $projectCreatedBy = model(ProjectModel::class)->where(['id' => $projectId])->getField('created_by');
        $addData = [
            'project_id' => $projectId,
            'user_id' => $projectCreatedBy,
            'role_code' => TaskService::PRODUCER_CODE,
        ];
        $res[] = model(ProjectMemberModel::class)->addItem($addData);

        // 剧集分配
        $episodeAllocationList = model(EpisodeAllocationModel::class)->where(['project_id' => $projectId])->field('user_id,role_code')->select();
        foreach ($episodeAllocationList as $episodeAllocation) {
            $addData = [
                'project_id' => $projectId,
                'user_id' => $episodeAllocation['user_id'],
                'role_code' => $episodeAllocation['role_code'],
            ];
            $res[] = model(ProjectMemberModel::class)->addItem($addData);
        }

        // 任务分配制片
        $assignProducerList = model(TaskModel::class)->where(['project_id' => $projectId])->field('assignee_producer')->group('assignee_producer')->select();
        $assignProducerList = array_column($assignProducerList, 'assignee_producer');
        foreach ($assignProducerList as $userId) {
            $userId = intval($userId);
            if ($userId <= 0) {
                continue;
            }
            $addData = [
                'project_id' => $projectId,
                'user_id' => $userId,
                'role_code' => TaskService::PRODUCER_CODE,
            ];
            $res[] = model(ProjectMemberModel::class)->addItem($addData);
        }
        // 任务分配组长
        $assignList = model(TaskModel::class)->where(['project_id' => $projectId])->field('assignee')->group('assignee')->select();
        $assignList = array_column($assignList, 'assignee');
        foreach ($assignList as $userId) {
            $userId = intval($userId);
            if ($userId <= 0) {
                continue;
            }
            $addData = [
                'project_id' => $projectId,
                'user_id' => $userId,
                'role_code' => TaskService::HEADMAN_CODE,
            ];
            $res[] = model(ProjectMemberModel::class)->addItem($addData);
        }

        // 任务执行人
        $executorList = model(TaskModel::class)
            ->where([
                'project_id' => $projectId,
                'executor' => ['GT', 0],
            ])
            ->where("executor_tenant_id = 0 or executor_tenant_id = tenant_id")
            ->field('executor')->group('executor')->select();
        $executorList = array_column($executorList, 'executor');
        foreach ($executorList as $userId) {
            $userId = intval($userId);
            if ($userId <= 0) {
                continue;
            }
            $addData = [
                'project_id' => $projectId,
                'user_id' => $userId,
                'role_code' => TaskService::ARTILIST_CODE,
            ];
            $res[] = model(ProjectMemberModel::class)->addItem($addData);
        }

        return $res;
    }

    /**
     * @param $userId
     * @return array
     */
    public function getProjectIdFilterByProjectMember($userId)
    {
        $projectIdList = model(ProjectMemberModel::class)
            ->where(['user_id' => $userId])
            ->field('project_id')
            ->where(['is_delete' => 0])
            ->group('project_id')->select();
        return array_column($projectIdList, 'project_id', 'project_id');
    }

//    /**
//     * 外包项目追加模块code
//     * @param int $projectId
//     * @param array $moduleCodes
//     * @throws Exception
//     */
//    public function appendModuleCodes(int $projectId, array $moduleCodes)
//    {
//        if (empty($moduleCodes)) {
//            return;
//        }
//        $shadowProjectData = model(ProjectModel::class)->where(['id' => $projectId])->field("id,module_codes")->find();
//        if (empty($shadowProjectData)) {
//            throw new Exception("project not found", ErrorCode::PROJECT_NOT_FOUND);
//        }
//        $currentModuleCodes = explode(',', $shadowProjectData['module_codes']);
//        $currentModuleCodes = array_merge($currentModuleCodes, $moduleCodes);
//        $currentModuleCodes = array_filter($currentModuleCodes, function ($item) {
//            return !empty($item);
//        });
//        $currentModuleCodes = trim(implode(',', array_unique($currentModuleCodes)), ',');
//        model(ProjectModel::class)->save(['id' => $projectId, 'module_codes' => $currentModuleCodes]);
//    }

    /**
     * 项目配置检测
     * @param $projectId
     * @return bool[]
     * @throws Exception
     */
    public function projectConfigCheck($projectId)
    {
        //项目目录,工作配置,UE引擎盘符
        $project = model(ProjectModel::class)->field('work_dir,work_dir_prefix,engine_dir_prefix,duration,rate,resolution')->find($projectId);
        //制片配置
        $projectMember = model(ProjectMemberModel::class)
            ->where(['project_id' => $projectId, 'role_code' => ["IN", "producer,step_producer,executive_producer,business_producer"]])->field('id')->find();

        $episode = model(EntityModel::class)->where(['project_id' => $projectId, 'module_id' => module_by_code('episode')['id']])->find();

        return [
            'duration' => !empty($project['duration']),
            'rate' => !empty($project['rate']),
            'resolution' => !empty($project['resolution']),
            'episode_number_config' => !empty($episode),
            'work_dir_config' => !empty($project['work_dir']),
            'work_dir_prefix_config' => !empty($project['work_dir_prefix']),
            'engine_dir_prefix_config' => !empty($project['engine_dir_prefix']),
            'project_producer_config' => !empty($projectMember),
        ];
    }


    /**
     * 项目环节消息接收人配置 union_id 列表获取
     * @throws Exception
     */
    public function projectConfigMessageReceiverUnionIdList($param, $groupByStepCategory = false)
    {
        $res = [];
        $projectReceiverConfig = self::projectConfigMessageReceiverFind($param);
        $userIdList = [];
        $grouped = [];
        foreach ($projectReceiverConfig as $stepCategoryReceiverConfig) {
            $currentStepCategoryId = $stepCategoryReceiverConfig['id'];
            if (!isset($grouped[$currentStepCategoryId])) {
                $grouped[$currentStepCategoryId] = [];
            }
            // 用户按环节打组
            foreach ($stepCategoryReceiverConfig['message_receiver_list'] ?? [] as $configItem) {
                $grouped[$currentStepCategoryId] = array_merge($grouped[$currentStepCategoryId], $configItem['user_list']);
            }
            // 组装所有用户id 查询map
            $userIdList = array_merge($userIdList, $grouped[$currentStepCategoryId]);
        }
        if (empty($userIdList)) {
            return [];
        }
        $userUnionIdList = model(UserModel::class)->where(['id' => ['in', $userIdList]])->field('id,union_id')->select();
        $userUnionIdMap = array_column($userUnionIdList, 'union_id', 'id');

        if (!$groupByStepCategory) {
            // 不打组的直接返回全部
            $res = array_unique(array_values($userUnionIdMap));
        } else {
            // 环节打组的筛选userMap键值
            foreach ($grouped as $stepCategoryId => $userList) {
                foreach ($userList as $userId) {
                    if (!isset($userUnionIdMap[$userId])) {
                        continue;
                    }
                    if (!isset($res[$stepCategoryId])) {
                        $res[$stepCategoryId] = [];
                    }
                    $res[$stepCategoryId][] = $userUnionIdMap[$userId];
                }
            }
        }
        return $res;
    }

    /**
     * 项目环节消息接收人配置 查询
     * @param $param
     * @return array
     * @throws Exception
     */
    public function projectConfigMessageReceiverFind($param)
    {
        // 参数定义
        $param = query_array_construct([
            'project_id' => 'intval',
            'step_category_id' => 'split_array|array_map,intval',
            'role_code_list' => 'split_array|array_map,strval',
        ], $param);

        // 消息接收人role列表
        $roleCodeList = [
            'headman',
            'executive_producer',
            'step_producer',
        ];
        if (!empty($param['role_code_list'] ?? [])) {
            $roleCodeList = array_intersect($roleCodeList, $param['role_code_list']);
        }

        $res = [];
        $project = model(ProjectModel::class)->where(['id' => $param['project_id']])->field('id,json')->find();
        if (empty($project)) {
            return $res;
        }
        // 当前消息接收人配置
        $json = json_decode($project['json'] ?? '', true);
        $currentConfig = $json['project_config_message_receiver'] ?? [];
        $currentConfig = array_column($currentConfig, null, 'step_category_id');

        // 获取环节，插入消息接收人信息
        $stepCategoryList = $this->getShowStepCategoryList($project['id']);

        $res = [];
        foreach ($stepCategoryList as $stepCategoryItem) {
            // 全部环节或指定环节
            if (empty($param['step_category_id']) || in_array($stepCategoryItem['id'], $param['step_category_id'])) {
                $messageReceiverListTemp = $currentConfig[$stepCategoryItem['id']]['receiver_list'] ?? [];
                $messageReceiverList = [];
                foreach ($messageReceiverListTemp as $receiverList) {
                    // 过滤指定接收人role
                    if (in_array($receiverList['role'], $roleCodeList)) {
                        $messageReceiverList[] = $receiverList;
                    }
                }
                // 消息接收人role的user_list补空值
                foreach (array_diff($roleCodeList, array_column($messageReceiverList, 'role')) as $role) {
                    $messageReceiverList[] = [
                        'role' => $role,
                        'user_list' => [],
                    ];
                }
                $res[] = array_merge($stepCategoryItem, [
                    'message_receiver_list' => $messageReceiverList
                ]);
            }
        }

        // 构造结果数组
        return result_array_construct([
            'id' => 'intval',
            'name' => 'strval',
            'code' => 'strval',
            'show_name' => 'strval',
            'message_receiver_list' => '',
        ], array_values($res), [
            'message_receiver_list' => []
        ]);
    }

    /**
     * 单项目单环节 消息接收人配置 保存
     * @param $param
     * @return array|bool
     * @throws Exception
     */
    public function projectConfigMessageReceiverSave($param)
    {
        $res = [];
        $project = model(ProjectModel::class)->where(['id' => $param['project_id']])->field('id,json')->find();
        if (empty($project)) {
            return $res;
        }
        $stepCategoryList = array_values(array_column($this->getShowStepCategoryList($project['id']), 'id'));
        if (!in_array($param['step_category_id'], $stepCategoryList)) {
            // 环节不存在
            return $res;
        }
        // 获取当前消息接收人配置
        $json = json_decode($project['json'] ?? '', true);
        $currentConfig = $json['project_config_message_receiver'] ?? [];
        $currentConfig = array_column($currentConfig, null, 'step_category_id');

        // 过滤合法输入，补充在当前消息接收人配置
        $validateReceiverList = fn($item) => (is_string($item['role']) && is_array($item['user_list']) && !empty($item['user_list']));
        $currentConfig[$param['step_category_id']] = [
            'step_category_id' => $param['step_category_id'],
            'receiver_list' => array_filter($param['receiver_list'], $validateReceiverList),
        ];

        $updateData = [
            'id' => $param['project_id'],
            'project_config_message_receiver' => array_values($currentConfig),
        ];
        $res = CommonService::instance('project')->update($updateData);
        return $res;
    }

    /**
     * 添加项目剧集
     * @param $data
     * @param $userId
     * @return mixed
     * @throws Exception
     * @throws \Exception
     */
    public function addProjectEpisode($data, $userId)
    {
        //查询出相应的集,去除掉重复的集
        $episode = $data['episode'];
        $projectId = $data['project_id'];

        //得出新集的集数
        $episodeData = $this->generateEpisode($projectId, $userId, $episode);
        $entityModel = model(EntityModel::class);

        $count = 0;
        if (!empty($episodeData['new_entity'])) {
            $count = $entityModel->addAll($episodeData['new_entity']);
        }

        //如果新增了集需要更改项目的剧集
        if (!empty($count)) {
            $episodeCount = model(EntityModel::class)->where(['project_id' => $projectId, 'module_id' => module_by_code('episode')['id']])->count();
            model(ProjectModel::class)->where(['id' => $projectId])->setField('episodes', $episodeCount);
        }


        return $episodeData['new_episode_count'];
    }

    /**
     * 获取新集
     * @param $projectId
     * @param $episode
     * @return array
     * @throws Exception
     */
    public function diffEpisode($projectId, $episode)
    {
        if (empty($episode)) {
            return [];
        }

        $episodeModuleId = module_by_code('episode')['id'];
        $episodeList = model(EntityModel::class)->where(['project_id' => $projectId, 'code' => ['IN', $episode], 'module_id' => $episodeModuleId])->field('code')->select();
        $episodeCodes = array_column($episodeList, 'code');
        return array_diff($episode, $episodeCodes);
    }

    /**
     * 生成剧集
     * @param $projectId
     * @param $userId
     * @param array $episodes
     * @return array
     * @throws Exception
     */
    public function generateEpisode($projectId, $userId, $episodes = [])
    {
        $tmp = [
            'new_entity' => [],
            'new_episode_count' => 0
        ];

        // 查询需要自动生成集数任务的模块
        $episodesTypeStepCategory = model(StepCategoryModel::class)
            ->field('id,name,code,entity_id')
            ->where(['entity_id' => module_by_code('episode')['id'], 'parent_id' => 0, 'is_show' => 'yes'])
            ->select();

        if (empty($episodesTypeStepCategory)) {
            return $tmp;
        }

        $stepCategoryCodes = [];
        $stepCategoryEntityIds = [];
        foreach ($episodesTypeStepCategory as $episodesTypeStepCategoryItem) {
            $stepCategoryCodes[] = $episodesTypeStepCategoryItem['code'];
            $stepCategoryEntityIds[] = $episodesTypeStepCategoryItem['entity_id'];
        }

        $stepWorkFlows = model(StepWorkflowModel::class)->where(['code' => ["in", $stepCategoryCodes]])->field('id,code')->select();
        $stepWorkFlows = array_column($stepWorkFlows, 'id', 'code');

        $project = model(ProjectModel::class)->field('episodes,tenant_id')->find($projectId);

        $entityEpisodeData = [];
        if (empty($episodes)) {
            $entityEpisodeData = model(EntityModel::class)->where(['project_id' => $projectId, 'entity_id' => ['IN', $stepCategoryEntityIds]])
                ->field('module_id')->group('module_id')->select();
            $entityEpisodeData = array_column($entityEpisodeData, 'module_id');

            $len = strlen((string)$project['episodes']);
            $len = $len > 3 ? $len : 3;
            for ($i = 1; $i <= $project['episodes']; $i++) {
                $episodes[] = "EP" . str_pad($i, $len, 0, STR_PAD_LEFT);
            }

        }

        //去除重复集,获取新集
        $newEpisodes = $this->diffEpisode($projectId, $episodes);

        $taskDefaultStatus = StatusService::getInstance()->getTaskDefaultStatus(); // 未开始
        // 创建新的集实体
        $newEntity = [];
        if (!empty($newEpisodes)) {
            foreach ($episodesTypeStepCategory as $episodesTypeStepCategoryItem) {
                //如果是自行生成,需要判断是否已经生成过,如果已经生成过,则不能再去自行生成
                if (empty($episodes) && in_array($episodesTypeStepCategoryItem['entity_id'], $entityEpisodeData)) {
                    continue;
                }
                foreach ($newEpisodes as $newEpisode) {
                    if ($episodesTypeStepCategoryItem['code'] == "episode") {
                        $code = $name = $newEpisode;
                    } else {
                        $name = "$newEpisode{$episodesTypeStepCategoryItem['name']}";
                        $code = generate_code($name);
                    }
                    $stepWorkflowId = $stepWorkFlows[$episodesTypeStepCategoryItem['code']] ?? 0;
                    $newEntity[] = [
                        'name' => $name,
                        'code' => $code,
                        'tenant_id' => $project['tenant_id'],
                        'project_id' => $projectId,
                        'module_id' => $episodesTypeStepCategoryItem['entity_id'],
                        'category_id' => $episodesTypeStepCategoryItem['id'],
                        'initial_episode_code' => $newEpisode,
                        'step_workflow_id' => $stepWorkflowId,
                        'created' => time(),
                        'created_by' => $userId,
                        'uuid' => create_uuid(),
                        'json' => json_encode([
                            $episodesTypeStepCategoryItem['code'] . '_grade' => "A",
                            $episodesTypeStepCategoryItem['code'] . '_status' => $taskDefaultStatus['id'],
                        ])
                    ];
                }
            }
        }

        return ['new_entity' => $newEntity, 'new_episode_count' => count($newEpisodes)];
    }

    /**
     * 更新项目
     * @param $data
     * @param $currentModuleData
     * @param $throwNoDataChangeException
     * @return array|bool|null
     * @throws Exception
     * @throws \Throwable
     */
    public function update($data, $currentModuleData, $throwNoDataChangeException)
    {
        $mediaModel = model(MediaModel::class);
        $mediaService = MediaService::getInstance();
        $instance = CommonService::instance($currentModuleData['code']);
        $projectModel = model(ProjectModel::class);
        // 检查是否修改work_dir
        if (!empty($data['work_dir'])) {
            $project = $projectModel->find($data['id']);
            $workDirPrefixCheck = $data['work_dir_prefix'] ?? $project['work_dir_prefix'];
            $existCheck = model(ProjectModel::class)->where(['work_dir' => $data['work_dir'], 'work_dir_prefix' => $workDirPrefixCheck, 'id' => ['NEQ', $data['id']], 'type' => 'insource'])->find();
            if (!empty($existCheck)) {
                throw new LogicException('work_dir already exist', ErrorCode::PROJECT_WORK_DIR_EXIST);
            }
        }
        $projectModel->startTrans();
        try {
            //填充图片
            if (isset($data['media'])) {
                $mediaService->generateMediaData($data['media']);
                $mediaData = $mediaModel->addItem($data['media']);
                if (!$mediaData) {
                    throw new LogicException($mediaModel->getError(), ErrorCode::MEDIA_ADD_FAILURE);
                } else {
                    $data['project_media'] = $mediaData['id'];
                }
                unset($data['media']);
            }

            // 更新集的计划时间
            if (!empty($data['start_time']) || !empty($data['end_time'])) {
                PlanService::getInstance()->projectPlanSave($data);
            }
            //同步外包配制 -- 是否打包上传
            $this->syncOutsourceProject($data);

            $resData = $instance->commonUpdate(["data" => $data], $currentModuleData, $throwNoDataChangeException);
            $projectModel->commit();
            if (!empty($resData['project_status'])) {
                $statusInfo = model(StatusModel::class)->where(['id' => $resData['project_status']])->find();
                $resData['status_info'] = $statusInfo;
            }
            return $resData;
        } catch (\Throwable $e) {
            $projectModel->rollback();
            throw $e;
        }
    }


    /**
     * 同步外包配制 -- 是否打包上传
     * @param array $data 项目数据
     * @return void
     */
    private function syncOutsourceProject(array $data)
    {
        //是否有传参 -- 是否打包上传
        $isPackUpload = $data['is_pack_upload'] ?? 0;
        if (!isset($data['is_pack_upload']) || !isset($data['id'])) {
            return;
        }
        $projectModel = model(ProjectModel::class);

        $projectInfo = $projectModel->where(['id' => $data['id'], 'type' => ProjectModel::TYPE_INSOURCE])->find();
        //判断是否修改
        if ($projectInfo['is_pack_upload'] == $isPackUpload) {
            return;
        }
        //同步外包项目配制 -- 是否打包上传
        $outSourceProList = $projectModel->where(['belong_project_id' => $data['id'], 'type' => ProjectModel::TYPE_OUTSOURCE])->field('id')->select();
        if ($outSourceProList) {
            $updateData = [];
            foreach ($outSourceProList as $val) {
                $updateData[] = [
                    'id' => $val['id'],
                    'is_pack_upload' => $isPackUpload
                ];
            }
            $projectModel->saveAll($updateData);
        }
    }


    /**
     * 删除项目剧集
     * @param $filter
     * @return false|int|mixed|string
     * @throws Exception
     */
    public function deleteProjectEpisode($filter)
    {
        $projectId = $filter['project_id'];
        $episodeModuleId = module_by_code('episode')['id'];
        $filter['module_id'] = $episodeModuleId;
        $episode = model(EntityModel::class)->where($filter)->select();

        $count = 0;
        if (empty($episode)) {
            return $count;
        }

        $episodeCodes = [];
        $episodeIds = [];
        foreach ($episode as $episodeItem) {
            $episodeCodes[$episodeItem['code']] = $episodeItem['code'];
            $episodeIds[] = $episodeItem['id'];
        }
        //判断该集下面是否存在关联的实体, 排除集
        $entityList = model(EntityModel::class)->where([
            'project_id' => $projectId,
            'initial_episode_code' => ['IN', $episodeCodes],
            'module_id' => ['NEQ', $episodeModuleId]
        ])->find();

        //该集下面没有关联的实体了,则可以进行删除
        if (!empty($entityList)) {
            throw new LogicException('The episode cannot be deleted', ErrorCode::THE_EPISODE_CANNOT_BE_DELETED);
        }

        $entityModel = model(EntityModel::class);
        $count = $entityModel->where(['id' => ['IN', $episodeIds]])->delete();

        //需要更新项目的剧集数
        if (!empty($count)) {
            $episodeCount = model(EntityModel::class)->where(['project_id' => $projectId, 'module_id' => module_by_code('episode')['id']])->count();
            model(ProjectModel::class)->where(['id' => $projectId])->setField('episodes', $episodeCount);
        }
        return $count;
    }

    /**
     * 同步用户角色 (删除掉project member中多余角色)
     * @param $tenantId
     * @param $role
     * @return array
     * @throws \Exception
     */
    public function syncUserRoleProjectMember($tenantId, $role)
    {
        $projectList = model(ProjectModel::class)->where(['tenant_id' => $tenantId])->select();
        $projectIds = array_column($projectList, 'id');

        $projectMember = model(ProjectMemberModel::class)
            ->where([
                "project_id" => ["IN", $projectIds], "role_code" => ['IN', $role]
            ])
            ->field('id,user_id,role_code')->select();

        $userList = TenantService::getInstance()->selectDepartmentUser($tenantId, [
            "param" => [
                "filter" => [
                    "role_code" => $role
                ]
            ]
        ]);

        $userRole = [];
        $userList = $userList['rows'];
        foreach ($userList as $userListItem) {
            $userRole[$userListItem['saas_user_id']] = array_column($userListItem['role_info'] ?? [], 'code');
        }

        $deleteProjectMemberIds = [];
        foreach ($projectMember as $projectMemberItem) {
            if (empty($userRole[$projectMemberItem['user_id']]) || !in_array($projectMemberItem['role_code'], $userRole[$projectMemberItem['user_id']])) {
                $deleteProjectMemberIds[] = $projectMemberItem['id'];
            }
        }

        if (!empty($deleteProjectMemberIds)) {
            model(ProjectMemberModel::class)->where(['id' => ['IN', $deleteProjectMemberIds]])->delete();
        }

        return $deleteProjectMemberIds;
    }

    /**
     * 查询项目集关系树
     * @param $param
     * @param int $userId
     * @return array
     * @throws Exception
     */
    public function selectProjectEpisodeAllocationTree($param, int $userId)
    {
        $userProjectList = $this->getUserProjectList($userId, $param);

        if ($userProjectList["total"] == 0) {
            return [];
        }

        $projectList = [];
        $projectIdList = [];
        foreach ($userProjectList['rows'] as $project) {
            $projectList[] = [
                'id' => $project['project']['id'],
                'name' => $project['project']['name'],
                'code' => $project['project']['code'],
                'real_id' => $project['project']['id'],
            ];
            $projectIdList[] = $project['project']['id'];
        }

        $entityList = model(EntityModel::class)
            ->where([
                    'project_id' => ['IN', $projectIdList],
                    'module_id' => module_by_code('episode')['id'],
                ]
            )
            ->field("id,name,project_id,code,id as real_id")->select();
        $entityMap = array_group_by($entityList, 'project_id');
        foreach ($projectList as &$project) {
            $projectId = $project['id'];
            $project['id'] = "project_" . $projectId;
            $project['children'] = $entityMap[$projectId] ?? [];
            $project['children'] = array_sort_by($project['children'] ?? [], 'code', 'asc', false);
        }

        return $projectList;
    }

    /**
     * 追加项目角色资源
     * @param array $res
     * @param int $userId
     * @param $appCode
     * @return void
     */
    private function appendProjectRoleResource(array &$res, int $userId, $appCode)
    {
        $userProjectMemberListFilter = [
            'project_id' => ['IN', array_column(array_column($res['rows'], 'project'), 'id')]
        ];
        $userProjectMemberList = ProjectMemberService::getInstance()->getUserProjectMemberRole($userId, $userProjectMemberListFilter);
        if (empty($userProjectMemberList)) {
            return;
        }
        $userProjectMemberMap = [];
        foreach ($userProjectMemberList as $userProjectMemberItem) {
            $userProjectMemberMap[$userProjectMemberItem['project_id']][] = $userProjectMemberItem['role_code'];
        }
        $data = ['param' => ['filter' => [
            'app_code' => $appCode,
        ]]];
        $userRoleResourcesGroupByRoleCode = teamones_request('teamones-oauth', 'resource/select_role_resource_group_by_role', $data);
        $userRoleResourcesGroupByRoleCode = $userRoleResourcesGroupByRoleCode['data'];
        if (empty($userRoleResourcesGroupByRoleCode)) {
            return;
        }
        foreach ($res['rows'] as &$row) {
            $projectRoleCodes = $userProjectMemberMap[$row['project']['id']] ?? [];
            if (empty($projectRoleCodes)) {
                continue;
            }
            $row['project_role_code'] = $projectRoleCodes;
            $intersectRoleResources = array_intersect_key($userRoleResourcesGroupByRoleCode, array_fill_keys($projectRoleCodes, 0));
            if (empty($intersectRoleResources)) {
                continue;
            }
            $tempResources = [];
            foreach ($intersectRoleResources as $roleCodeResources) {
                foreach ($roleCodeResources as $resource) {
                    $tempResources[$resource['id']] = $resource;
                }
            }

            $row['project_role_resources'] = array_values($tempResources);
        }
    }


    /**
     * 获取项目统计工序分类配置
     * @return array
     */
    public function statisticalStepCategoryConfig()
    {
        return TemplateService::getInstance()->findStatisticalStepTemplate();
    }

    /**
     * 查询计划和实际人力资源
     * @param $filter
     * @return array
     */
    public function selectProduceResources($filter)
    {
        $project = model(ProjectModel::class)->where(['id' => $filter['id']])->find();
        if (empty($project)) {
            throw_strack_exception("Project not found", ErrorCode::PROJECT_NOT_FOUND);
        }
        $this->selectDownstreamRelateTenant($project, $projectList, $tenantData);
        $tenantIds = $tenantData['tenant_group_member_ids'] ?? [];
        $insideList = [];
        $supplierList = [];
        foreach ($projectList as $item) {
            if (in_array($item['tenant_id'], $tenantIds)) {
                $insideList[] = $item['id'];
            } else {
                $supplierList[] = $item['id'];
            }
        }
        if ($insideList) {
            $projectMemberQuery = model(ProjectMemberModel::class)
                ->where(['project_id' => ['IN', $insideList]])
                ->group("user_id")
                ->buildSql();
            $projectMemberCount = model(ProjectMemberModel::class)->execute($projectMemberQuery);
        }

        // 查询成片已完成集数
        $episodeModule = module_by_code("episode");
        $completeVideoFilter = [
            "project_id" => $project['id'],
            'module_id' => $episodeModule['id'],
            'code' => ['LIKE', "EP%"],
            "_string" => "json->>'$.episode_is_delete' is null or json->>'$.episode_is_delete' = 'no'"
        ];

        $completeVideoCount = model(EntityModel::class)
            ->where($completeVideoFilter)
            ->where(["json->>'$.episode_is_seal'" => 'yes'])
            ->where(["json->>'$.episode_is_show'" => 'yes'])
            ->count();

        // 查询剧本完成集数
        $scriptRequestData = [
            'param' => [
                'filter' => ['project_id' => $project['id']],
                'fields' => 'episode_id,project_id'
            ]
        ];
        $episodeEntityCount = 0;
        $scriptResData = teamones_request('teamones-script', 'script/select', $scriptRequestData)['data'];
        $episodeIds = array_unique(array_column($scriptResData, 'episode_id'));
        if (!empty($episodeIds)) {
            $episodeEntityCount = model(EntityModel::class)->where(['module_id' => $episodeModule['id'], 'id' => ['IN', $episodeIds]])->field('id')->count();
        }

        $statusData = model(StatusModel::class)->where(['code' => 'final'])->field('id,code')->select();
        $statusDataMap = array_column($statusData, null, 'code');

        // 查询资产完成集数
        $assetFinalEpisodeData = model(EntityModel::class)
            ->where(['project_id' => $project['id'], 'module_id' => module_by_code('design')['id']])
            ->where("json_unquote(json_extract(json, '$.design_status')) = {$statusDataMap['final']['id']}")
            ->field('entity.initial_episode_code')
            ->group('entity.initial_episode_code')
            ->select();
        $assetFinalEpisodeCount = count($assetFinalEpisodeData);

        $projectJsonArr = json_decode($project['json'], true);
        if (empty($projectJsonArr['spent'])) {
            $projectJsonArr['spent'] = 0;
        }
        if (empty($projectJsonArr['invested'])) {
            $projectJsonArr['invested'] = 0;
        }
        $completeVideo = [
            'final_count' => $completeVideoCount
        ];
        $script = [
            'final_count' => $episodeEntityCount
        ];
        $asset = [
            'final_count' => $assetFinalEpisodeCount
        ];
        $res['plan_resource'] = $project['plan_resource'];
        $res['resource']['inside'] = $projectMemberCount ?? 0;
        $res['resource']['supplier'] = count($supplierList);
        $res['complete_video'] = $completeVideo; // 成片
        $res['script'] = $script; // 剧本
        $res['asset'] = $asset; // 资产
        $res['spent'] = (int)$projectJsonArr['spent']; // 已花费
        $res['invested'] = (int)$projectJsonArr['invested']; // 已投入
        return $res;
    }

    /**
     * 查询人力资源分布
     * @param $filter
     * @return array
     */
    public function selectProduceResourcesDistributed($filter)
    {
        // 根据主项目找到下发的其他外包项目，获取到所有有关的租户ID
        $project = model(ProjectModel::class)->where(['id' => $filter['id']])->find();
        if (empty($project)) {
            throw_strack_exception("Project not found", ErrorCode::PROJECT_NOT_FOUND);
        }
        // 根据可选参数查询到所有有关租户的工序分类和地理位置
        $options = ['select_tenant_step_category' => 'yes', 'select_tenant_address' => 'yes'];
        $this->selectDownstreamRelateTenant($project, $projectList, $tenantData, $options);
        $tenantIds = $tenantData['tenant_group_member_ids'] ?? [];
        $tenantStepCategoryList = $tenantData['tenant_step_category'] ?? [];
        $tenantAddressList = $tenantData['tenant_address'] ?? [];
        $stepCategoryIds = $tenantData['step_category_ids'] ?? [];
        // 区分哪些租户是供应商还是集团内部成员
        $res['inside'] = [];
        $res['supplier'] = [];
        $insideList = [];
        foreach ($projectList as $item) {
            if (!empty($tenantAddressList)) {
                $tenantAddressList[$item['tenant_id']]['coordinate'] = $tenantAddressList[$item['tenant_id']]['coordinate'] ? explode(',', $tenantAddressList[$item['tenant_id']]['coordinate']) : [];
            }
            if (in_array($item['tenant_id'], $tenantIds)) {
                $insideList[] = $item['id'];
                $res['inside'][$item['tenant_id']]['project_id'] = $item['id'];
                $res['inside'][$item['tenant_id']]['address'] = $tenantAddressList[$item['tenant_id']] ?? null;
                if ($tenantStepCategoryList) {
                    $res['inside'][$item['tenant_id']]['step_category'] = join(',', $tenantStepCategoryList[$item['tenant_id']]);
                } else {
                    $res['inside'][$item['tenant_id']]['step_category'] = "";
                }
            } else {
                $res['supplier'][$item['tenant_id']]['address'] = $tenantAddressList[$item['tenant_id']] ?? null;
                if ($tenantStepCategoryList) {
                    $res['supplier'][$item['tenant_id']]['step_category'] = join(',', $tenantStepCategoryList[$item['tenant_id']]);
                } else {
                    $res['supplier'][$item['tenant_id']]['step_category'] = "";
                }
            }
        }
        $res['inside'] = array_values($res['inside']);
        $res['supplier'] = array_values($res['supplier']);
        $projectMember = [];
        // 计算出每个内部项目里面的人数
        if ($insideList) {
            $projectMember = model(ProjectMemberModel::class)
                ->field("project_id,count(distinct user_id) as user_count")
                ->where(['project_id' => ['IN', $insideList]])
                ->group("project_id")
                ->select();
        }
        $projectMember = array_column($projectMember, null, 'project_id');
        // 获取所涉及到的工序分类
        if ($stepCategoryIds) {
            $res['step_category'] = model(StepCategoryModel::class)->field('id,name')->where(['id' => ['IN', $stepCategoryIds]])->select();
        }
        foreach ($res['inside'] as &$item) {
            if (!empty($projectMember[$item['project_id']])) {
                $item['project_produce'] = $projectMember[$item['project_id']]['user_count'];
            }
        }
        return $res;
    }

    /**
     * 查询下游项目关联的租户
     * @param $project
     * @param $projectList
     * @param $tenantData
     * @param array $options
     * @return void
     */
    private function selectDownstreamRelateTenant($project, &$projectList, &$tenantData, $options = [])
    {
        // 找到上游项目是本项目的数据
        $projectFilter = [
            'belong_project_id' => $project['id'],
            'id' => $project['id'],
            '_logic' => 'or'
        ];
        $projectList = model(ProjectModel::class)->where($projectFilter)->select();
        $projectTenantIds = array_column($projectList, 'tenant_id');
        // 查找在集团内的公司
        $requestData = ['param' => ['filter' => [
            'project_tenant_id' => $project['tenant_id'],
            'all_project_tenant_id' => $projectTenantIds,
            'options' => $options
        ]]];
        $tenantData = teamones_request('teamones-im', 'tenant_group_member/get_tenant_group_member_by_project', $requestData)['data'];
    }

    /**
     * 查询项目显示列表
     * @param $projectId
     * @param bool $includeStep
     * @return array
     * @throws Exception
     */
    public function getShowStepCategoryList($projectId, $includeStep = false)
    {
        $res = TemplateService::getInstance()->findStepCategoryShowList();
        if ($includeStep) {
            $stepCategoryIdList = array_column($res, 'id');
            $stepList = model(StepModel::class)->where(['step_category_id' => ['IN', $stepCategoryIdList]])->select();
            $stepList = array_group_by($stepList, 'step_category_id');
            foreach ($res as &$i) {
                $i['children'] = $stepList[$i['id']] ?? [];
            }
        }
        return $res;
    }

    /**
     * 独立项目-添加成员
     * @param $data
     * @return array|bool|null
     * @throws Exception
     */
    public function projectInitiationAddUser($data)
    {
        $roleId = 0;
        // 匹配code_id
        if (!empty($data['role_code'])) {
            $requestData = [
                'param' => [
                    'filter' => [
                        'code' => $data['role_code']
                    ],
                    'fields' => 'id,code'
                ]
            ];
            $roleData = teamones_request('teamones-im', 'role/find', $requestData)['data'];
            $roleId = $roleData['id'] ?? 0;  // 当前角色
        }

        $data = [
            'type' => $data['type'] ?? ProjectMemberModel::PRODUCTION_TYPE,
            'user_id' => $data['user_id'],
            'role_id' => $roleId,
            'role_code' => $data['role_code'] ?? null,
            'project_id' => $data['project_id'],
            'step_category_ids' => $data['step_category_ids'] ?? '',
            'is_delete' => 0,
        ];
        $where = [];

        // 环节不能出现重复用户
        if ($data['type'] == ProjectMemberModel::PRODUCTION_TYPE) {
            // 生产组-环节和角色不能同时存在
            if (!empty($data['role_code']) && !empty($data['step_category_ids'])) {
                throw new LogicException("production type role_code step_category_ids Can't exist at the same time", ErrorCode::PROJECT_MEMBER_USER_ID_ADD_FAIL);
            }

            if (!empty($data['role_code'])) { // 生产组-角色没有环节
                $data['step_category_ids'] = '';
            } else {
                $data['role_code'] = TaskService::ARTILIST_CODE; // 如果是生产组,那么默认用户角色是艺术家
            }
            $where = [
                'user_id' => $data['user_id'],
                'project_id' => $data['project_id'],
                'role_code' => $data['role_code'],
                'type' => $data['type']
            ];
            if (!empty($data['step_category_ids'])) { // 生产组角色不能有环节
                $where['step_category_ids'] = $data['step_category_ids'];
            }
        }
        if ($data['type'] != ProjectMemberModel::PRODUCTION_TYPE) {
            $data['step_category_ids'] = ''; // 管理组不需要环节分类
            $where = [
                'user_id' => $data['user_id'],
                'role_code' => $data['role_code'],
                'project_id' => $data['project_id'],
                'type' => $data['type']
            ];
        }

        $projectMemberUser = model(ProjectMemberModel::class)
            ->where($where)
            ->field('id, is_delete')
            ->find();
        if (!empty($projectMemberUser)) {
            $projectMemberIdExist = $projectMemberUser['id'];
            if ($projectMemberUser['is_delete'] == '0') {
                throw new LogicException('project member user_id must not repeat', ErrorCode::PROJECT_MEMBER_USER_ID_ADD_FAIL);
            } else {
                $updateData = [
                    'id' => $projectMemberUser['id'],
                    'is_delete' => 0,
                ];
                return CommonService::instance("project_member")->update($updateData);
            }
        }
        return CommonService::instance("project_member")->create($data);
    }

    /**
     * 同步角色ID
     * @return int
     */
    public function syncProjectMemberRoleId()
    {
        $roleCode = model(ProjectMemberModel::class)->field('role_code')->group('role_code')->column('role_code');
        // 匹配code_id
        $requestData = [
            'param' => [
                'filter' => [
                    'role.code' => ['-in', join(',', $roleCode)]
                ]
            ]
        ];
        $roleData = teamones_request('teamones-im', 'role/select', $requestData)['data'];
        $roleData = $roleData['rows'];

        $updateSql = 'UPDATE `project_member` ';
        $updateSql .= 'set `role_id` =  CASE `role_code`';
        foreach ($roleData as $value) {
            $updateSql .= sprintf("WHEN '%s' THEN %d ", $value['code'], $value['id']);
        }
        $updateSql .= 'ELSE 0 END ';
        $updateSql .= 'WHERE `role_id`=0';
        return model(ProjectMemberModel::class)->execute($updateSql);
    }

    /**
     * 项目立项-管理列表
     * @return array
     * @throws \think\Exception
     * @throws \Exception
     */
    public function manageList($filter)
    {

        $isQueryDepartment = $filter['is_query_department'] ?? "no";

        // 类型筛选
        $type = $filter['type'] ?? null;

        $allRoleIds = '';
        // 读取项目立项角色配置
        $projectInitiationData = OptionsService::getInstance()->getOptionsData("project_initiation");
        if (!empty($projectInitiationData)) {
            foreach ($projectInitiationData as &$projectInitiationItem) {
                $allRoleIds .= ',' . $projectInitiationItem;
                $projectInitiationItem = explode(',', $projectInitiationItem);
            }
        }
        $allRoleIds = trim($allRoleIds, ',');

        $requestData = [
            'param' => [
                'filter' => [
                    'role.id' => ['-in', $allRoleIds]
                ]
            ]
        ];
        $roleData = teamones_request('teamones-im', 'role/select', $requestData)['data'];
        $roleData = $roleData['rows'];
        $roleIdMap = array_column($roleData, null, 'id');

        // 多项目查询
        $showProducerStepCategoryConfigArrayGroup = ProjectService::getInstance()->getShowStepCategoryGroup();

        $productionChildrenIdArr = [];
        $stepCategoryIds = [];
        // 处理合并项目模板的显示分类
        foreach ($showProducerStepCategoryConfigArrayGroup as $projectTemplateStepCategoryValue) {
            $stepCategoryIdArr = $projectTemplateStepCategoryValue['children_ids'];
            $stepCategoryIds = array_merge($stepCategoryIds, $stepCategoryIdArr);
            sort($stepCategoryIdArr);
            $productionChildrenIdArr[] = [
                'id' => $projectTemplateStepCategoryValue['name'],
                'category' => 'step_category',
                'type' => ProjectMemberModel::PRODUCTION_TYPE,
                'role_code' => null,
                'role_name' => $projectTemplateStepCategoryValue['name'],
                'children' => null,
                'step_category_ids' => join(',', $stepCategoryIdArr),
                'step_category_list' => $stepCategoryIdArr
            ];
        }

        //是否查询生产组部门
        if ($isQueryDepartment == "yes" && !empty($stepCategoryIds)) {
            $stepCategoryIds = array_unique($stepCategoryIds);
            $requestData = [
                "param" => [
                    "filter" => [
                        "is_query_tenant_group_department" => "yes",
                        "step_category_id_list" => join(",", $stepCategoryIds),
                        "is_management" => "no"
                    ],
                    "fields" => "id,name,step_category_ids,tenant_id"
                ]
            ];
            $departmentList = teamones_request('teamones-im', 'department/base_select', $requestData)['data'];
            foreach ($departmentList as &$departmentItem) {
                $departmentItem['step_category_list'] = explode(",", $departmentItem['step_category_ids']);
            }

            foreach ($productionChildrenIdArr as $productionChildrenIdArrIndex => $productionChildrenIdArrItem) {
                foreach ($departmentList as $item) {
                    //判定是否有交集
                    $intersectList = array_intersect($item["step_category_list"], $productionChildrenIdArrItem['step_category_list']);

                    if (!empty($intersectList)) {
                        $item['parent_id'] = $productionChildrenIdArrItem['id'];
                        $item['role_name'] = $item['name'];
                        if (empty($productionChildrenIdArrItem['children'])) {

                            $productionChildrenIdArrItem['children'] = [$item];
                        } else {
                            $productionChildrenIdArrItem['children'][] = $item;
                        }
                    }
                }
                $productionChildrenIdArr[$productionChildrenIdArrIndex] = $productionChildrenIdArrItem;
            }
        }

        $returnData = [];
        foreach ($projectInitiationData as $roleCode => $roleGroupIds) {
            $childrenIdArr = array_map(function ($roleId) use ($roleIdMap, $roleCode) {
                $returnData['category'] = 'role';
                $returnData['type'] = $roleCode;
                $returnData['role_code'] = $roleIdMap[$roleId]['code'];
                $returnData['role_name'] = $returnData['id'] = $roleIdMap[$roleId]['name'];
                $returnData['children'] = null;
                return $returnData;
            }, $roleGroupIds);

            if ($roleCode == ProjectMemberModel::PRODUCTION_TYPE) {
                $childrenIdArr = array_merge($childrenIdArr, $productionChildrenIdArr);
            }

            if (isset($type) && $type == $roleCode) {
                return $childrenIdArr;
            }

            $returnData[] = [
                'type' => $roleCode,
                'role_code' => $roleCode,
                'role_name' => ProjectMemberModel::TYPE_LIST[$roleCode],
                'children' => $childrenIdArr,
            ];
        }
        return $returnData;
    }

    /**
     *获取项目工序配置
     * @param $projectId
     * @return array
     */
    public function getProducerStepCategoryConfig($projectId)
    {
        $producerStepCategoryConfig = OptionsService::getInstance()->getProducerStepCategoryConfig(false);
        if (empty($producerStepCategoryConfig)) {
            return [];
        }
        // 过滤掉不展示的工序分类
        $showProducerStepCategoryConfig = array_filter($producerStepCategoryConfig, function ($tmp) {
            return $tmp['is_show'] == 'yes';
        });
        if (empty($showProducerStepCategoryConfig)) {
            return [];
        }
        return array_group_by($showProducerStepCategoryConfig, 'show_name');
    }

    /**
     * 获取显示工序分类组
     * @return array
     * @throws \think\Exception
     */
    public function getShowStepCategoryGroup()
    {
        $res = TemplateService::getInstance()->findStepCategoryShowList();
        $group = [];
        foreach ($res as $resItem) {
            $resItem['link_module_code'] = 'step_category';
            if (empty($group[$resItem['show_name']])) {
                $group[$resItem['show_name']] = [
                    "id" => $resItem['show_name'],
                    "name" => $resItem['show_name'],
                    'link_module_code' => 'show_step_category',
                    "children" => [$resItem],
                    "children_ids" => [$resItem['id']]
                ];
            } else {
                $group[$resItem['show_name']]['children'][] = $resItem;
                $group[$resItem['show_name']]['children_ids'][] = $resItem['id'];
            }
        }

        return array_values($group);
    }

    /**
     * 查询产能配置
     * @param int $projectId
     * @param array $stepCategoryIdList
     * @param array $data
     * @param bool $withFormat
     * @return array
     * @throws Exception
     */
    public function getPCConfig(int $projectId, array $stepCategoryIdList, $data = [], $withFormat = true)
    {
        $projectConfig = model(ProjectModel::class)->find($projectId);
        if (empty($projectConfig)) {
            throw new LogicException("project not found", ErrorCode::PROJECT_NOT_FOUND);
        }
        $config = json_decode($projectConfig['config'], true);
        $configList = $config['pc_config'] ?? [];

        $anotherConfig = [];
        $stepCategoryConfigList = [];
        foreach ($configList as $configItem) {
            if (!isset($configItem['step_category_id'])) {
                continue;
            }
            if (!in_array($configItem['step_category_id'], $stepCategoryIdList)) {
                $anotherConfig[] = $configItem;
            } else {
                $stepCategoryConfigList[] = $configItem;
            }
        }
        $data = array_column($data, null, 'group_uk');

        $stepCategoryConfigList = array_column($stepCategoryConfigList, null, 'group_uk');
        $stepCategoryIds = implode(',', $stepCategoryIdList);
        $topLevelStepCategoryList = model(StepCategoryModel::class)
            ->where("id in (select parent_id from step_category where id in ({$stepCategoryIds}))")
            ->select();
        if (!empty($topLevelStepCategoryList)) {
            $moduleIdList = array_column($topLevelStepCategoryList, 'entity_id');
            ProjectCapacityReportService::getInstance()->validatePCModuleList($moduleIdList);
            $moduleCode = module_by_id($moduleIdList[0])['code'];
        } else {
            $moduleCode = TaskService::DIRECTOR_CODE;
        }

        $configTemplateList = ProjectCapacityReportService::getInstance()->getModulePCConfigTemplate($moduleCode, $stepCategoryIdList);
        foreach ($configTemplateList as &$configTemplate) {
            if (isset($data[$configTemplate['group_uk']])) {
                $configTemplate['number'] = $data[$configTemplate['group_uk']]['number'];
            } else {
                $configTemplate['number'] = $stepCategoryConfigList[$configTemplate['group_uk']]['number'] ?? 0;
            }
            $configTemplate['number'] = intval($configTemplate['number']);
            $anotherConfig[] = $configTemplate;
        }
        $config['pc_config'] = $anotherConfig;
        CommonService::instance('project')->update(['id' => $projectId, 'config' => $config], 'project', false);
        if (!$withFormat) {
            return $configTemplateList;
        }
        return ProjectCapacityReportService::getInstance()->formatModulePCConfigData($moduleCode, $configTemplateList);
    }

    /**
     * 保存配置
     * @param int $projectId
     * @param array $stepCategoryIdList
     * @param array $data
     * @return array
     * @throws Exception
     */
    public function savePCConfig(int $projectId, array $stepCategoryIdList, array $data)
    {
        return $this->getPCConfig($projectId, $stepCategoryIdList, $data);
    }


    /**
     * 查询产能配置
     * @param int $projectId
     * @param array $stepCategoryIdList
     * @param array $data
     * @param bool $withFormat
     * @return array
     * @throws Exception
     */
    public function getPCLevelTransConfig(int $projectId, array $stepCategoryIdList, $data = [], $withFormat = true)
    {
        $projectConfig = model(ProjectModel::class)->field('id,config')->find($projectId);
        if (empty($projectConfig)) {
            throw new LogicException("project not found", ErrorCode::PROJECT_NOT_FOUND);
        }
        $config = json_decode($projectConfig['config'], true);
        $configList = $config['pc_level_config'] ?? [];

        $anotherConfig = [];
        $stepCategoryConfigList = [];
        foreach ($configList as $configItem) {
            if (!isset($configItem['step_category_id'])) {
                continue;
            }
            if (!in_array($configItem['step_category_id'], $stepCategoryIdList)) {
                $anotherConfig[] = $configItem;
            } else {
                $stepCategoryConfigList[] = $configItem;
            }
        }
        $data = array_column($data, null, 'group_uk');

        $stepCategoryConfigList = array_column($stepCategoryConfigList, null, 'group_uk');
        $moduleCode = ProjectCapacityReportService::getInstance()->getStepCategoryListModuleCode($stepCategoryIdList);

        $configTemplateList = ProjectCapacityReportService::getInstance()->getModulePCLevelConfigTemplate($moduleCode, $stepCategoryIdList);
        foreach ($configTemplateList as &$configTemplate) {
            if (isset($data[$configTemplate['group_uk']])) {
                $configTemplate['number'] = $data[$configTemplate['group_uk']]['number'];
            } else {
                $configTemplate['number'] = $stepCategoryConfigList[$configTemplate['group_uk']]['number'] ?? 0;
            }
            $configTemplate['number'] = intval($configTemplate['number']);
            $anotherConfig[] = $configTemplate;
        }
        $config['pc_level_config'] = $anotherConfig;
        CommonService::instance('project')->update(['id' => $projectId, 'config' => $config], 'project', false);
        if (!$withFormat) {
            return $configTemplateList;
        }
        return ProjectCapacityReportService::getInstance()->formatModulePCConfigData($moduleCode, $configTemplateList);
    }

    /**
     * 保存配置
     * @param int $projectId
     * @param array $stepCategoryIdList
     * @param array $data
     * @return array
     * @throws Exception
     */
    public function savePCLevelTransConfig(int $projectId, array $stepCategoryIdList, array $data)
    {
        return $this->getPCLevelTransConfig($projectId, $stepCategoryIdList, $data);
    }

    /**
     * 查询资产分类的等级转换map
     * @param int $projectId
     * @param array $stepCategoryIdList
     * @param string $assetCategory
     * @return array
     * @throws Exception
     */
    public function getAssetCategoryTransConfig(int $projectId, array $stepCategoryIdList, string $assetCategory)
    {
        $ruleList = $this->getPCLevelTransConfig($projectId, $stepCategoryIdList, [], false);
        $ruleList = array_filter($ruleList, function ($i) use ($assetCategory) {
            return strstr($i['group_uk'], $assetCategory) !== false;
        });
        $tMap = [];
        foreach ($ruleList as $rule) {
            $tMap[$rule['number_logic']] = $rule['number'];
        }
        return $tMap;
    }

    /**
     * 转换成C
     * @param $tMap
     * @param $grade
     * @param $number
     * @return float|int|mixed
     */
    public function toC($tMap, $grade, $number)
    {
        $nextLevel = $this->getNextLevel($grade);
        if (empty($nextLevel)) {
            return $number;
        }
        if (!empty($tMap['low_to_' . $nextLevel])) {
            $number = $tMap['low_to_' . $nextLevel] * $number;
        }
        if ($grade != 'C') {
            return $this->toC($tMap, $nextLevel, $number);
        } else {
            return $number;
        }
    }

    /**
     * 获得下一个等级
     * @param $grade
     * @return string|null
     */
    private function getNextLevel($grade)
    {
        $res = null;
        switch ($grade) {
            case "S+":
                $res = "S";
                break;
            case "S":
                $res = "A";
                break;
            case "A":
                $res = "B";
                break;
            case "B":
                $res = "C";
                break;
            case "C":
            default:
        }
        return $res;
    }

    /**
     * 查询产能配置
     * @param $projectIdList
     * @param array $stepCategoryIdList
     * @return array
     * @throws Exception
     */
    public function getMultiPCConfig($projectIdList, array $stepCategoryIdList)
    {
        $projectConfigList = model(ProjectModel::class)->where(['id' => ['IN', $projectIdList]])->select();
        if (empty($projectConfigList)) {
            throw new LogicException("project not found", ErrorCode::PROJECT_NOT_FOUND);
        }

        $stepCategoryIds = implode(',', $stepCategoryIdList);
        $topLevelStepCategoryList = model(StepCategoryModel::class)
            ->where("id in (select parent_id from step_category where id in ({$stepCategoryIds}))")
            ->select();
        if (!empty($topLevelStepCategoryList)) {
            $moduleIdList = array_column($topLevelStepCategoryList, 'entity_id');
            ProjectCapacityReportService::getInstance()->validatePCModuleList($moduleIdList);
            $moduleCode = module_by_id($moduleIdList[0])['code'];
        } else {
            $moduleCode = TaskService::DIRECTOR_CODE;
        }
        $configTemplateList = ProjectCapacityReportService::getInstance()->getModulePCConfigTemplate($moduleCode, $stepCategoryIdList);

        $res = [];
        foreach ($projectConfigList as $projectConfig) {
            $config = json_decode($projectConfig['config'], true);
            $configList = $config['pc_config'] ?? [];
            $stepCategoryConfigList = [];
            foreach ($configList as $configItem) {
                if (!isset($configItem['step_category_id'])) {
                    continue;
                }
                if (in_array($configItem['step_category_id'], $stepCategoryIdList)) {
                    $stepCategoryConfigList[] = $configItem;
                }
            }

            $stepCategoryConfigList = array_column($stepCategoryConfigList, null, 'group_uk');
            foreach ($configTemplateList as &$configTemplate) {
                $configTemplate['number'] = $stepCategoryConfigList[$configTemplate['group_uk']]['number'] ?? 0;
                $configTemplate['number'] = intval($configTemplate['number']);
            }

            $res[$projectConfig['id']] = $configTemplateList;
        }
        return $res;
    }

    /**
     * 解析项目单集分钟数
     * @param $projectDuration
     * @return int
     */
    public function getProjectDuration($projectDuration)
    {
        if (empty($projectDuration)) {
            // 默认14分钟
            return 14;
        }
        list($hour, $minutes) = explode(":", $projectDuration);
        $hour = intval($hour);
        $minutes = intval($minutes);
        if ($hour > 0) {
            $minutes += $hour * 60;
        }
        return $minutes;
    }

    /**
     * 批量创建项目成员
     * @param int $projectId
     * @param string $type
     * @param $userList
     * @return array
     * @throws \Exception
     */
    public function batchCreateProjectMemberV2(int $projectId, string $type, $userList)
    {
        $res = [];
        /**
         * 在一个项目内 一个组内 一个角色下 用户应该是唯一的
         */
        $users = model(ProjectMemberModel::class)
            ->where(['project_id' => $projectId, 'type' => $type])
            ->select();
        $usersGrouped = array_group_by($users, 'role_code');
        foreach ($usersGrouped as &$users) {
            $users = array_group_by($users, 'step_category_ids');
            foreach ($users as &$usersGroupedList) {
                $usersGroupedList = array_column($usersGroupedList, null, 'user_id');
            }
        }
        $addData = [];
        $updateIdData = [];
        foreach ($userList as $user) {
            $roleCode = $user['role_code'] ?? '';
            $stepCategoryIds = $user['step_category_ids'] ?? '';
            $roleId = $user['role_id'] ?? 0;
            if (!empty($usersGrouped[$roleCode][$stepCategoryIds][$user['user_id']])) {
                if ($usersGrouped[$roleCode][$stepCategoryIds][$user['user_id']]['is_delete'] != 0) {
                    $updateIdData[] = $usersGrouped[$roleCode][$stepCategoryIds][$user['user_id']]['id'];
                }
                continue;
            }
            $tmpValue = [ // 去除重复的数组
                'project_id' => $projectId,
                'role_code' => $roleCode,
                'role_id' => $roleId,
                'user_id' => $user['user_id'],
                'type' => $type,
                'step_category_ids' => $stepCategoryIds,
                'uuid' => create_uuid(),
            ];
            $usersGrouped[$roleCode][$stepCategoryIds] = $tmpValue;
            $addData[] = $tmpValue;
        }
        if (!empty($addData)) {
            $res = model(ProjectMemberModel::class)->addAll($addData);
        }
        if (!empty($updateIdData)) {
            model(ProjectMemberModel::class)->where(['id' => ['IN', $updateIdData]])->save(['is_delete' => 0]);
        }

        return $res;
    }

    /**
     * @param $projectId
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function getProjectByBelongProjectId($projectId)
    {
        // 根据项目id 判断是否关联 belogn_project_id
        $project = model(ProjectModel::class)
            ->field(['id'])
            ->where(["belong_project_id" => $projectId])
            ->where(["JSON_UNQUOTE(JSON_EXTRACT(json,'$.project_status'))" => ['IN', [1, 3, 4, 7]]])
            ->select();

        array_push($project, intval($projectId));

        return array_unique(array_column($project, 'id'));
    }
}
